#include "FileCrypt/file_crypt.h"


void encryptFile(unsigned char* inputFIleName, unsigned char* outputFileName, AES_KEY* encryptionKey)
{

	FILE* fin = fopen(inputFIleName,"rb");
	if(!fin)
		perror("Error opening Encryption input file");

	FILE* fout = fopen(outputFileName,"wb");
	if(!fout)
		perror("Error opening Encryption output file\n");
		
    	
	writeHeaderToOutputFile(fout,fin);
	
	
	char* buffer = malloc(128);
	char* cipher = malloc(128);

	long bytesRead = 0;
	long byteCount = 0;
	
	size_t encryptedBytes = 0;
	while(1)
	{
		//read input
		bytesRead = fread(buffer,1,128,fin);
		byteCount += bytesRead;

		if(bytesRead == 0)
			break;

//		printf("Enc: Bytes Read = %d\n",bytesRead);
		
		//encrypt
		encryptedBytes = aes_data_encrypt(cipher,buffer,encryptionKey,bytesRead);
		
		if(encryptedBytes < 0)
			printf("Error encrypting the file\n");
			
//			printf("Encrypted Bytes = %u\n",(unsigned int)encryptedBytes);

		//write encrypted data to output file		
		fwrite(cipher,1,encryptedBytes,fout);
		
		//End of Read
		if(bytesRead < 128)
			break;
	}
	
	if(byteCount == getFileSize(fin))
		;//printf("Encryption Successful. Total Bytes Encrypted = %ld\n",byteCount);
	else
		printf("Encryption Failed\n");
	
	
	fclose(fin);
	fclose(fout);
	
	free(buffer);
	free(cipher);


}

//0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020

void decryptFile(unsigned char* inputFIleName,unsigned char* outputFileName, AES_KEY* decryptionKey)
{

	printf("Decrypting file...\n");
	
	FILE* fin = fopen(inputFIleName,"rb");
	if(!fin)
		perror("Error opening Decryption input file");

	FILE* fout = fopen(outputFileName,"wb");
	if(!fout)
		perror("Error opening Decryption output file\n");
		
		
    
	EncFileHeader header;
	readHeaderFromInputFIle(fin, &header);

	
	char* buffer = malloc(128);
	char* message = malloc(128);

	long bytesRead = 0;
	long byteCount = 0;
	while(1)
	{
		//read binary input
		bytesRead = fread(buffer,1,128,fin);
		byteCount += bytesRead;
		

		//decrypt
		aes_data_decrypt(message,buffer,decryptionKey,bytesRead);
		
		//printf("Decrypted Message = %s\n",message);
		//write decrypted data to output file		
		fwrite(message,1,bytesRead,fout);
		//printf( "%u\n",(unsigned int)fwrite(message,1,bytesRead,fout));
		
		//End of Read
		if(bytesRead < 128)
			break;
	}
	
	if(byteCount == getFileSize(fin)-sizeof(EncFileHeader) )
		;//printf("Decryption Successful. Total Bytes Decrypted = %ld\n",byteCount);
	else
		printf("Decryption Failed\n");
	
	

	
	
	fclose(fin);
	fclose(fout);

	
	//shave off the padded bytes
	//solution for unix only
	
	#ifdef UNIX_based
		int truncateStat = -1;	

//		int fileDescriptor = fileno(fout); /*http://stackoverflow.com/questions/3167298/how-can-i-convert-a-file-pointer-file-to-a-file-descriptor-fd*/
//		truncateStat = ftruncate(fileDescriptor,100);

		truncateStat  = truncate(outputFileName,header.MessageSize);
		
		if(truncateStat == 0)
		{
			printf("Padding removed successfully\n");
		}
		else
		{
			printf("Error removing padding\n");
		}
	#endif

	
	
	free(buffer);
	free(message);


}

void readHeaderFromInputFIle(FILE* input_file, EncFileHeader* header)
{

long bytesRead;
bytesRead = fread(header, 1 , sizeof(EncFileHeader) , input_file);

if(bytesRead != sizeof(EncFileHeader) )
	perror("Error Reading Encryption File Header");

}

void writeHeaderToOutputFile(FILE* output_file, FILE* message_file)
{
//ceate a header structure
EncFileHeader header;
header.MessageSize = getFileSize(message_file);

char userNotes[] = "Encryption Test Header"
				  "May be required to add checksum of the message in Header in future implementations";
				  
memcpy(&header.userNotes, userNotes , sizeof(userNotes));

fwrite(&header,1,sizeof(EncFileHeader),output_file);

}

long getFileSize(FILE* file)
{
	fseek(file,0L,SEEK_END);
	long size = ftell(file);
	rewind(file);
	return size;
}


/*Encryption Functions*/

int aes_data_encrypt(char* cipher,const char* message,AES_KEY* encryptionKey,size_t size)
{

	int bytesToPad = findBytesToPad(size);
	
//	printf("Bytes to pad = %d\n",bytesToPad);
//	printf("Message to encrypt = %s\n",message);
	
	size+=bytesToPad;
	
	char* messageToEncrypt = calloc(size,1);
	if(messageToEncrypt == NULL)
		perror("message to encrypt failed");

	
	if(bytesToPad != 0)
		addPaddingToData( message, messageToEncrypt ,size-bytesToPad , bytesToPad);
	else
	    memcpy(messageToEncrypt,message,size);
	

	
    char* enc  = calloc(size,1);
    
    if(enc == NULL)
        printf("Error: Out of memory\n");


    unsigned int i = 0;
    
    for( i = 0; i < size ; i+=16)
    {
        memcpy( enc+i , messageToEncrypt + i , 16);
        AES_encrypt(enc+i, cipher+i,  encryptionKey);
    }
		
        free(enc);
        free(messageToEncrypt);
        
        return size;
}




int aes_data_decrypt(char* message,const char* cipher, AES_KEY* decryptionKey ,size_t size)
{

    char* dec = calloc(size,1);

    if(dec == NULL){
        printf("Error: Out of memory\n");
        return -1;
    }
    
    unsigned int i = 0;
   
     for( i =0 ; i < size ; i+=16)
    {
        memcpy(dec+i, cipher+i , 16);
        AES_decrypt( dec+i, message+i ,  decryptionKey);
    }
        free(dec);
        
    return size;
}


int findBytesToPad(size_t size)
{
    if(size > (1024L * 1024L *1024L))
        printf("Error: cannot handle more than 1 GB file size\n");

    //pad to multiple of 128
    
    int bytesToPad = 0;
    
    
    if(size <= 128)
		bytesToPad = 128 - size;
    else
    {
		int i=0;
		int temp = size;
		for(i = 0; ; i++)
		{
		//increment till we get a multiple of 128
			temp++;
			float checkMult = fmod(temp,128.0); 
			
			if(checkMult > 0.0000000000000)
				continue;
				
			bytesToPad = i+1;
			break;
		}
	}

	return bytesToPad;

}


void addPaddingToData(const char* input, char* output , size_t currentSize,size_t numberOfBytesToPad)
{

	memcpy(output, input , currentSize);

	
	int i=0;
	char *paddingData = calloc(1,1);
	for(i = numberOfBytesToPad ; i >= 0 ;i--)
	{
		*paddingData = numberOfBytesToPad;
		memcpy(output+currentSize, paddingData , 1);
	}

}






